﻿using System;
using System.Collections.Generic;
using System.IO;

using NMemcached.Client.Configuration;

namespace NMemcached.Client.Binary
{
    /// <summary>
    /// Represents a binary protocol response
    /// </summary>
    internal class BinaryResponse : BinaryPacket
    {
        /// <summary>
        /// Identifies the packet as a response
        /// </summary>
        internal override PacketType Type { get { return PacketType.Response; } }

        /// <summary>
        /// Gets the response code for the operation
        /// </summary>
        internal ResponseCode ResponseCode { get; private set; }

        private BinaryResponse()
        {
            //The parse methods must be used to create response packets
        }

        /// <summary>
        /// Creates a collection of BinaryResponse objects from a collection of server responses
        /// </summary>
        /// <param name="responses">A collection of byte arrays that contain the server responses</param>
        /// <param name="endian">The byte order to use when reading primitives from the responses</param>
        /// <returns>A collection of BinaryResponse objects from a collection of server responses</returns>
        internal static IEnumerable<BinaryResponse> Parse(IEnumerable<byte[]> responses, Endian endian)
        {
            List<BinaryResponse> responseList = new List<BinaryResponse>();
            BinaryResponse currentResponse = null;
            foreach (byte[] response in responses)
            {
                currentResponse = BinaryResponse.Parse(response, endian);
                if (currentResponse != null)
                {
                    responseList.Add(currentResponse);
                }
            }
            return responseList;
        }

        /// <summary>
        /// Creates a collection of BinaryResponse objects from a byte stream
        /// </summary>
        /// <param name="inputStream">The server resonse stream from which the responses are read</param>
        /// <param name="endian">The byte order to use when reading primitives from the responses</param>
        /// <returns>A collection of BinaryResponse objects from the server response stream</returns>
        internal static IEnumerable<BinaryResponse> Parse(Stream inputStream, Endian endian)
        {
            List<BinaryResponse> packets = null;
            if (inputStream != null && inputStream.CanRead)
            {
                using (EndianBinaryReader reader = new EndianBinaryReader(inputStream, endian))
                {
                    BinaryResponse current = null;
                    packets = new List<BinaryResponse>();
                    while (reader.BaseStream.Position < reader.BaseStream.Length)
                    {
                        current = new BinaryResponse();

                        //Verify the "magic" byte is valid
                        byte magic = reader.ReadByte();
                        if (magic != (byte)PacketType.Response)
                        {
                            throw new ApplicationException(StringTable.Get("error_InvalidResponseCode"));
                        }

                        current.Operation = (OpCode)reader.ReadByte();
                        ushort keyLen = reader.ReadUInt16();
                        byte extrasLen = reader.ReadByte();
                        current.DataType = (DataTypeCode)reader.ReadByte();
                        current.ResponseCode = (ResponseCode)reader.ReadUInt16();

                        uint valueLen = reader.ReadUInt32();
                        //Value length includes extras and key length so subtract those two
                        valueLen -= (uint)(keyLen + extrasLen);

                        current.Opaque = reader.ReadUInt32();
                        current.Cas = reader.ReadUInt64();

                        if (extrasLen > 0)
                        {
                            current.Extras = new byte[extrasLen];
                            reader.Read(current.Extras, 0, current.Extras.Length);
                        }

                        if (keyLen > 0)
                        {
                            current.Key = new byte[keyLen];
                            reader.Read(current.Key, 0, current.Key.Length);
                        }

                        if (valueLen > 0)
                        {
                            current.Value = new byte[valueLen];
                            reader.Read(current.Value, 0, current.Value.Length);
                        }
                        packets.Add(current);
                    }
                }
            }
            return packets;
        }

        /// <summary>
        /// Creates a BinaryResponse from a byte array that contains a server response
        /// </summary>
        /// <param name="data">The byte array server response</param>
        /// <param name="endian">The byte order to use when reading primitives from the responses</param>
        /// <returns>The response object populated from the server response</returns>
        internal static BinaryResponse Parse(byte[] data, Endian endian)
        {
            using (MemoryStream inputStream = new MemoryStream(data))
            {
                inputStream.Position = 0;

                using (EndianBinaryReader reader = new EndianBinaryReader(inputStream, endian))
                {
                    BinaryResponse packet = new BinaryResponse();

                    //Verify the "magic" byte is valid
                    byte magic = reader.ReadByte();
                    if (magic != (byte)PacketType.Response)
                    {
                        throw new ApplicationException(StringTable.Get("error_InvalidResponseCode"));
                    }

                    packet.Operation = (OpCode)reader.ReadByte();
                    ushort keyLen = reader.ReadUInt16();
                    byte extrasLen = reader.ReadByte();
                    packet.DataType = (DataTypeCode)reader.ReadByte();
                    packet.ResponseCode = (ResponseCode)reader.ReadUInt16();

                    uint valueLen = reader.ReadUInt32();
                    //Value length includes extras and key length so subtract those two
                    valueLen -= (uint)(keyLen + extrasLen);

                    packet.Opaque = reader.ReadUInt32();
                    packet.Cas = reader.ReadUInt64();

                    if (extrasLen > 0)
                    {
                        packet.Extras = new byte[extrasLen];
                        reader.Read(packet.Extras, 0, packet.Extras.Length);
                    }

                    if (keyLen > 0)
                    {
                        packet.Key = new byte[keyLen];
                        reader.Read(packet.Key, 0, packet.Key.Length);
                    }

                    if (valueLen > 0)
                    {
                        packet.Value = new byte[valueLen];
                        reader.Read(packet.Value, 0, packet.Value.Length);
                    }

                    return packet;
                }
            }
        }
    }
}
